﻿//*** Guidance generated code ***//
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;

using ProjectBase.Core;
using ProjectBase.Core.Web;

namespace ProjectBase.Mvc
{
    /// <summary>
    ///     Base class for smart Controls serving the Entities properties.
    ///     The TSourceProperty can be used to restrict the types to be passed to
    ///     the SetSourceProperty() (e.g. only bool for checkbox).
    /// </summary>
    /// <typeparam name = "TSourceProperty"></typeparam>
    /// <typeparam name = "TPropertyControl"></typeparam>
    public abstract class PropertyControl<TSourceProperty, TPropertyControl> : VisualControl<ICoreModel>, IPropertyControl
        where TPropertyControl : PropertyControl<TSourceProperty, TPropertyControl>
    {
        #region members
        string _inputName;
        string _prefix;
        bool? _showReadOnly;
        #endregion members

        #region constructor
        protected PropertyControl(bool? asReadonly = null)
        {
            _showReadOnly = asReadonly;
        }
        protected PropertyControl(string prefix)
        {
            _prefix = prefix;
        }
        #endregion constructor

        #region Set SourceProperty
        /// <summary>
        ///     Extracts the value, name and type of the source property from the source property expression
        ///     and sets control properties accordingly
        /// </summary>
        /// <typeparam name = "TResult">Type of source property</typeparam>
        /// <param name = "propertyExpression">Source property expression</param>
        /// <returns></returns>
        public virtual TPropertyControl SetSourceProperty<TResult>(Expression<Func<TResult>> propertyExpression)
            where TResult : TSourceProperty
        {
            if (propertyExpression.IsNull())
            {
                return this as TPropertyControl;
            }

            if (propertyExpression.NodeType != ExpressionType.Lambda)
            {
                throw new ArgumentException("Lambda expression expected", "propertyExpression");
            }

            MemberExpression body;
            switch (propertyExpression.Body.NodeType)
            {
                case ExpressionType.Convert:
                    var unnary = propertyExpression.Body as UnaryExpression;
                    if (unnary.Operand.NodeType
                        != ExpressionType.MemberAccess)
                    {
                        throw new NotSupportedException(
                            "Provided 'Expression<Func<TResult>> propertyExpression' parameter must be of a 'ExpressionType.MemberAccess'. ");
                    }
                    body = unnary.Operand as MemberExpression;
                    break;

                case ExpressionType.MemberAccess:
                    body = propertyExpression.Body as MemberExpression;
                    break;

                default:
                    throw new NotSupportedException(
                        "Provided 'Expression<Func<TResult>> propertyExpression' parameter must be of a 'ExpressionType.MemberAccess'. ");
            }

            if (body.IsNull())
            {
                throw new ArgumentException("MemberAccess or Convert type expression expected on the right side",
                    "propertyExpression");
            }

            ID = body.Member.Name;
            Type = typeof(TResult);

            OnSourcePropertyChanged(ID, propertyExpression.Compile().Invoke());

            return this as TPropertyControl;
        }

        protected abstract void OnSourcePropertyChanged(string propertyName, TSourceProperty value);
        #endregion Set SourceProperty

        #region Render
        protected override void BuildControls()
        {
            base.BuildControls();

            if (ShowReadOnly)
            {
                ReadControls();
            }
            else
            {
                WriteControls();
            }
        }

        protected abstract void ReadControls();
        protected abstract void WriteControls();
        #endregion Render

        #region public Set()
        public TPropertyControl SetCssClassName(string cssClassName)
        {
            if (cssClassName.IsNotEmpty())
            {
                SetAttribute(Attr.Class, cssClassName);
            }
            return this as TPropertyControl;
        }

        public TPropertyControl SetPrefix(string prefix)
        {
            Prefix = prefix;
            return this as TPropertyControl;
        }

        /// <summary>
        ///     The name attribute of the INPUT element (write mode)
        ///     (if not set, ID is used).
        ///     Name could (mostly is) used for a binding on controller request processing.
        /// </summary>
        public virtual TPropertyControl SetInputName(string inputName)
        {
            InputName = inputName;
            ID = inputName;
            return this as TPropertyControl;
        }

        /// <summary>
        ///     Sets (dynamically) the ShowReadOnly property
        ///     Decides if Write or Read element is rendered.
        ///     Default value is taken from IViewModel (action == detail ? read : write )
        /// </summary>
        /// <param name = "showReadOnly"></param>
        /// <returns>this</returns>
        public virtual TPropertyControl SetReadOnly(bool showReadOnly)
        {
            ShowReadOnly = showReadOnly;
            return this as TPropertyControl;
        }

        /// <summary>
        ///     Sets (dynamically) the Format property. It is used only when SetSourceProperty() was called.
        ///     Allows to format 'ValueTypes' e.g.:
        ///     "N2" will result in the two decimal places with culture dependent thousand separators
        ///     "d" will provide only date, "G" date with time
        /// </summary>
        /// <param name = "format"></param>
        /// <returns>this</returns>
        public virtual TPropertyControl SetFormat(string format)
        {
            Format = format;
            return this as TPropertyControl;
        }
        #endregion public Set()

        #region properties
        /// <summary>
        ///     Decides if Write or Read element is rendered.
        ///     Default value is taken from IViewModel (action == detail ? read : write )
        /// </summary>
        public virtual bool ShowReadOnly
        {
            get
            {
                if (_showReadOnly.Is()) // ShowReadOnly propety is set and wins
                {
                    return _showReadOnly.Value;
                }
                var webModel = Model as IViewModel;
                if (webModel.Is())
                {
                    return webModel.IsActionDetail();
                }
                return true;
            }
            set { _showReadOnly = value; }
        }

        /// <summary>
        ///     The name attribute of the INPUT element (write mode)
        ///     (if not set, ID is used).
        ///     Name could (mostly is) used for a binding on controller request processing.
        /// </summary>
        public virtual string InputName
        {
            get { return Prefix + (_inputName.IsEmpty() ? ID : _inputName); }
            set { _inputName = value; }
        }

        /// <summary>
        ///     Allows to format 'ValueTypes' as:
        ///     short, int, long, decimal, DateTime.
        ///     "N2" will result in the two decimal places with culture dependent thousand separators
        ///     "d" will provide only date, "G" date with time to seconds detail
        /// </summary>
        public virtual string Format { get; set; }

        /// <summary>
        ///     Contains the type of TResult from the parsed SetSourceProperty()
        /// </summary>
        protected virtual Type Type { get; set; }

        protected virtual string Prefix
        {
            get
            {
                if (_prefix.IsEmpty())
                {
                    return string.Empty;
                }
                return _prefix + (_prefix.EndsWith(Special.Dot) ? string.Empty : Special.Dot);
            }
            set { _prefix = value; }
        }
        #endregion properties
    }
}